package c.c.d.s.s.o.da.as.configuration.support.client;

import c.c.d.s.s.o.da.as.domain.client.dto.ClientDTO;
import c.c.d.s.s.o.da.as.repository.client.ClientMapper;
import cn.caplike.data.redis.service.spring.boot.starter.RedisKey;
import cn.caplike.data.redis.service.spring.boot.starter.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.stereotype.Service;

import java.util.Objects;

/**
 * 自定义的 {@link ClientDetailsService}
 *
 * @author LiKe
 * @version 1.0.0
 * @date 2020-06-15 12:41
 */
@Slf4j
@Service
public class CustomClientDetailsService implements ClientDetailsService {

    /**
     * client-details 缓存前缀
     */
    private static final String CACHE_PREFIX_CLIENT_DETAILS = "client-details";

    private ClientMapper clientMapper;

    private RedisService redisService;

    /**
     * Description: 从数据库中获取已经注册过的客户端信息<br>
     * Details: 该方法会在整个认证过程中被多次调用, 所以应该缓存. 和令牌的过期时间一致.
     *
     * @param clientId 客户端 ID
     * @see ClientDetailsService#loadClientByClientId(String)
     */
    @Override
    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
        log.debug("About to produce ClientDetails with client-id: {}", clientId);

        // ~ 对于 Authorization Server 来说, 请求它的客户端只可能是:
        //   1. 第一方客户端的前端和第一方客户端的后端: TODO 需要定义 client-id 和 client-secret
        //   2. 第三方客户端 (信息位于权限体系表里边)
        // -------------------------------------------------------------------------------------------------------------

        // =============================================================================================================

        // ~ 第三方客户端, 从权限体系表 / 缓存中获取
        // -------------------------------------------------------------------------------------------------------------

        final RedisKey cacheKey = RedisKey.builder().prefix(CACHE_PREFIX_CLIENT_DETAILS).suffix(clientId).build();

        // 先从缓存中获取 ClientDto
        ClientDTO clientDto = redisService.getValue(cacheKey, ClientDTO.class);
        // 如果缓存中没有, 从数据库查询并置入缓存
        if (Objects.isNull(clientDto)) {
            clientDto = clientMapper.getClient(clientId);

            if (Objects.isNull(clientDto)) {
                throw new ClientRegistrationException(String.format("客户端 %s 尚未注册!", clientId));
            }

            redisService.setValue(cacheKey, clientDto, clientDto.getAccessTokenValidity());
        }

        return new CustomClientDetails(clientDto);
    }

    // ~ Autowired
    // -----------------------------------------------------------------------------------------------------------------

    @Autowired
    public void setClientMapper(ClientMapper clientMapper) {
        this.clientMapper = clientMapper;
    }

    @Autowired
    public void setRedisService(RedisService redisService) {
        this.redisService = redisService;
    }
}
